using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Text;

namespace BeeldBank.Toolkit
{
    [Serializable]
    public class UniqueStringList : IList, IEnumerable, ISerializable
    {
        #region Constants
        protected const int DefaultSize = 16;
        #endregion Constants

        #region Static
        public static readonly UniqueStringList Empty = ReadOnly(new UniqueStringList());

        public static UniqueStringList ReadOnly(UniqueStringList list)
        {
            if (list == null) throw new ArgumentNullException("list");
            return new ReadOnlyWrapper(list);
        }

        public static UniqueStringList Synchronized(UniqueStringList list)
        {
            if (list == null) throw new ArgumentNullException("list");
            return new SynchronizedWrapper(list);
        }
        #endregion Static

        #region Fields
        protected ArrayList _data;                             // Holds the actual strings themselves and provides IList interface
        protected Hashtable _lookup;                           // Allows quick lookup
        protected string _emptyAlias = System.String.Empty;    // Defines the string, if any, that will automatically replace ""
        #endregion Fields

        #region Constructors
        protected UniqueStringList(SerializationInfo info, StreamingContext context)
            : this(info.GetInt32("count"))
        {
            // Retrieve the strings and add them into the new collection
            AddRange((string[])info.GetValue("strings", typeof(Array)));

            // Retrieve the empty alias
            _emptyAlias = info.GetString("emptyAlias");
        }

        internal UniqueStringList(bool trash) { }

        public UniqueStringList() : this(DefaultSize) { }

        public UniqueStringList(string[] values)
            : this(values.Length)
        {
            // Add the initial values into the pre-sized collection
            AddRange(values);
        }

        public UniqueStringList(UniqueStringList values)
            : this(values.Count)
        {
            // Add the initial values into the pre-sized collection
            AddRange(values.ToArray());
        }

        public UniqueStringList(int capacity)
        {
            // Create new containers to the indicated size
            _data = new ArrayList(capacity);
            _lookup = new Hashtable(capacity, CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
        }
        #endregion Constructors

        #region Implementation of ISerializable
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // Save the info required to recreate this object
            // Count is not actually required but including it means that the initial collections can be pre-sized
            info.AddValue("count", Count);
            info.AddValue("strings", ToArray());
            info.AddValue("emptyAlias", _emptyAlias);
        }
        #endregion Implementation of ISerializable

        #region Implementation of IEnumerable
        public virtual IEnumerator GetEnumerator()
        {
            return _data.GetEnumerator();
        }
        #endregion Implementation of IEnumerable

        #region Implementation of IList
        int IList.Add(object value)
        {
            // Return -1 if the value is null or it is an empty string that EmptyAlias would translate to null
            if (value == null || (((string)value).Length == 0 && EmptyAlias == null)) return -1;

            // Since the class Add does not return an index (for speed) we can use its return value to determine the position in the list
            return Add((string)value) == true ? Count - 1 : IndexOf((string)value);
        }

        bool IList.Contains(object value)
        {
            return Contains((string)value);
        }

        int IList.IndexOf(object value)
        {
            return IndexOf((string)value);
        }

        void IList.Insert(int index, object value)
        {
            Insert(index, (string)value);
        }

        void IList.Remove(object value)
        {
            Remove((string)value);
        }

        object IList.this[int index]
        {
            get { return this[index]; }
            set { this[index] = (string)value; }
        }

        public virtual bool IsFixedSize
        {
            get { return false; }
        }
        #endregion

        #region Implementation of ICollection
        public virtual void CopyTo(System.Array array, int index)
        {
            _data.CopyTo(array, index);
        }

        public virtual bool IsSynchronized
        {
            get { return false; }
        }

        public virtual int Count
        {
            get { return _data.Count; }
        }

        public virtual object SyncRoot
        {
            get { return _data.SyncRoot; }
        }
        #endregion Implementation of ICollection

        #region General Implementation
        public virtual bool Add(string value)
        {
            // Translate an empty string to its alias
            if (value != null && value.Length == 0) value = EmptyAlias;

            // Ignore null strings
            if (value == null) return false;

            if (_lookup.Contains(value))
                // String already exists so do nothing
                return false;
            else
            {
                // Insert the string at the end of the list
                _data.Insert(Count, value);

                // Add it to the lookup table
                _lookup.Add(value, value);

                // Indicate that the string was added
                return true;
            }
        }

        public virtual void AddRange(string[] values)
        {
            if (values != null) foreach (string value in values) Add(value);
        }

        public virtual void AddRange(UniqueStringList values)
        {
            if (values != null) foreach (string value in values) Add(value);
        }

        public virtual string[] AlsoIn(UniqueStringList secondList)
        {
            ArrayList commonItems = new ArrayList();

            if (secondList != null)
            {
                foreach (string item in this)
                {
                    if (secondList.Contains(item)) commonItems.Add(item);
                }
            }

            return (string[])commonItems.ToArray(typeof(string));
        }

        public virtual void Clear()
        {
            // Clear all the strings
            _data.Clear();

            // Clear the lookup table
            _lookup.Clear();
        }

        public virtual bool Contains(string value)
        {
            return value == null ? false : _lookup.Contains(value);
        }

        public virtual string EmptyAlias
        {
            get
            {
                return _emptyAlias;
            }
            set
            {
                // Ignore if the value is the same as the current value
                if (value != _emptyAlias)
                {
                    // Replace any existing "" with the new alias (or remove if the alias is null)
                    if (value == null)
                        Remove("");
                    else
                    {
                        int originalEmptyIndex = IndexOf("");
                        if (originalEmptyIndex != -1)
                        {
                            RemoveAt(originalEmptyIndex);
                            Insert(originalEmptyIndex, value);
                        }
                    }
                    // Save the new value
                    _emptyAlias = value;
                }
            }
        }

        public virtual int IndexOf(string value)
        {
            // Use the lookup table (for speed) to check whether the item actually exists
            if (!_lookup.Contains(value))
                // It doesn't so we can return the value directly
                return -1;
            else
            {
                // Retrieve the index - can't use IndexOf since this is case sensitive
                for (int i = 0; i < _data.Count; i++)
                {
                    if (string.Compare(value, (string)_data[i], true) == 0) return i;
                }

                return -1;
            }
        }

        public virtual void Insert(int index, string value)
        {
            // Translate an empty string to its alias
            if (value != null && value.Length == 0) value = EmptyAlias;

            // Ignore any nulls
            if (value == null) return;

            // Use the lookup table (for speed) to check whether the item already exists
            if (_lookup.Contains(value))
            {
                // It does so get the index of the current string
                int currentIndex = IndexOf(value);

                // String is already in correct position so return immediately
                if (index == currentIndex) return;

                // Remove the existing string from its old position
                _data.RemoveAt(currentIndex);

                // Insert the string at the new position adjusting if the new index is further down in the list
                // than the original string
                _data.Insert(index > Count ? index - 1 : index, value);
            }
            else
            {
                // Insert the string at the appropriate index
                _data.Insert(index, value);

                // Add it to the lookup table
                _lookup.Add(value, value);
            }
        }

        public virtual bool IsCaseSensitive { get { return false; } }

        public virtual bool IsReadOnly { get { return false; } }

        public virtual string[] NotIn(UniqueStringList secondList)
        {
            ArrayList commonItems = new ArrayList();

            if (secondList != null)
            {
                foreach (string item in this)
                {
                    if (!secondList.Contains(item)) commonItems.Add(item);
                }
            }
            return (string[])commonItems.ToArray(typeof(string));
        }

        public virtual void Remove(string value)
        {
            // Translate an empty string to its alias
            if (value != null && value.Length == 0) value = EmptyAlias;

            // Ignore any nulls
            if (value == null) return;

            // See if the string is present (lookup table is much faster then the ArrayList)
            if (_lookup.Contains(value))
            {
                // Remove the string
                _data.Remove(value);

                // Remove also from the lookup table
                _lookup.Remove(value);
            }
        }

        public virtual void RemoveAt(int index)
        {
            // Get the string at the specified index
            string item = (string)_data[index];

            // Remove the string from the data list
            _data.RemoveAt(index);

            // Then remove it from the lookup table
            _lookup.Remove(item);
        }

        public virtual void RemoveRange(string[] values)
        {
            if (values != null) foreach (string value in values) Remove(value);
        }

        public virtual void RemoveRange(UniqueStringList values)
        {
            if (values != null) RemoveRange(values.ToArray());
        }

        public virtual void Reverse(int index, int count)
        {
            _data.Reverse(index, count);
        }

        public virtual void Reverse()
        {
            _data.Reverse();
        }

        public virtual void Sort()
        {
            _data.Sort();
        }

        public virtual string this[int index]
        {
            get { return (string)_data[index]; }
            set { Insert(index, value); }
        }

        public virtual string[] ToArray()
        {
            return (string[])_data.ToArray(typeof(string));
        }

        public override string ToString()
        {
            return ToString(System.String.Empty, ";", System.String.Empty);
        }

        public virtual string ToString(string prefix, string separator, string suffix)
        {
            StringBuilder sb = new StringBuilder(prefix);

            foreach (string item in this)
            {
                if (sb.Length > prefix.Length) sb.Append(separator);
                sb.Append(item);
            }

            sb.Append(suffix);

            return sb.ToString();
        }

        #endregion General Implementation

        #region Debug
#if DEBUG
        public string DebugWatch
        {
            get { return ToString(); }
        }
#endif
        #endregion

        [Serializable]
        private sealed class SynchronizedWrapper : UniqueStringList, ISerializable, IDeserializationCallback
        {
            #region Fields
            private UniqueStringList _wrappedList;               // Holds the wrapped object
            private object _syncRoot;                            // Reference to the wrapped object's syncRoot
            #endregion Fields

            #region Constructors
            internal SynchronizedWrapper(UniqueStringList list)
                : base(true)
            {
                _wrappedList = list;
                _syncRoot = _wrappedList.SyncRoot;
            }

            private SynchronizedWrapper(SerializationInfo info, StreamingContext context)
            {
                _wrappedList = (UniqueStringList)info.GetValue("wrappedList", typeof(UniqueStringList));
            }

            void IDeserializationCallback.OnDeserialization(object caller)
            {
                _syncRoot = _wrappedList.SyncRoot;
            }
            #endregion Constructors

            #region Implementation of ISerializable
            void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
            {
                info.AddValue("wrappedList", _wrappedList);
            }
            #endregion

            #region Implementation of IEnumerable
            public override IEnumerator GetEnumerator()
            {
                lock (_syncRoot)
                {
                    return _wrappedList.GetEnumerator();
                }
            }
            #endregion

            #region Implementation of IList
            public override bool IsFixedSize
            {
                get { return _wrappedList.IsFixedSize; }
            }
            #endregion

            #region Implementation of ICollection
            public override void CopyTo(System.Array array, int index)
            {
                lock (_syncRoot)
                {
                    _wrappedList.CopyTo(array, index);
                }
            }

            public override bool IsSynchronized { get { return true; } }

            public override int Count
            {
                get
                {
                    lock (_syncRoot)
                    {
                        return _wrappedList.Count;
                    }
                }
            }

            public override object SyncRoot
            {
                get { return _wrappedList.SyncRoot; }
            }
            #endregion Implementation of ICollection

            #region General Implementation
            public override bool Add(string value)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.Add(value);
                }
            }

            public override string[] AlsoIn(UniqueStringList secondList)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.AlsoIn(secondList);
                }
            }

            public override void Clear()
            {
                lock (_syncRoot)
                {
                    _wrappedList.Clear();
                }
            }

            public override bool Contains(string value)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.Contains(value);
                }
            }

            public override string EmptyAlias
            {
                get
                {
                    lock (_syncRoot)
                    {
                        return _wrappedList.EmptyAlias;
                    }
                }
                set
                {
                    lock (_syncRoot)
                    {
                        _wrappedList.EmptyAlias = value;
                    }
                }
            }

            public override int IndexOf(string value)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.IndexOf(value);
                }
            }

            public override void Insert(int index, string value)
            {
                lock (_syncRoot)
                {
                    _wrappedList.Insert(index, value);
                }
            }

            public override bool IsCaseSensitive
            {
                get { return _wrappedList.IsCaseSensitive; }
            }

            public override bool IsReadOnly
            {
                get { return _wrappedList.IsReadOnly; }
            }

            public override string[] NotIn(UniqueStringList secondList)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.NotIn(secondList);
                }
            }

            public override void Remove(string value)
            {
                lock (_syncRoot)
                {
                    _wrappedList.Remove(value);
                }
            }

            public override void RemoveAt(int index)
            {
                lock (_syncRoot)
                {
                    _wrappedList.RemoveAt(index);
                }
            }

            public override void Reverse()
            {
                lock (_syncRoot)
                {
                    _wrappedList.Reverse();
                }
            }

            public override void Reverse(int index, int count)
            {
                lock (_syncRoot)
                {
                    _wrappedList.Reverse(index, count);
                }
            }

            public override void Sort()
            {
                lock (_syncRoot)
                {
                    _wrappedList.Sort();
                }
            }

            public override string this[int index]
            {
                get
                {
                    lock (_syncRoot)
                    {
                        return _wrappedList[index];
                    }
                }
                set
                {
                    lock (_syncRoot)
                    {
                        _wrappedList[index] = value;
                    }
                }
            }

            public override string[] ToArray()
            {
                lock (_syncRoot)
                {
                    return _wrappedList.ToArray();
                }
            }

            public override string ToString(string prefix, string separator, string suffix)
            {
                lock (_syncRoot)
                {
                    return _wrappedList.ToString(prefix, separator, suffix);
                }
            }

            #endregion General Implementation
        }

        [Serializable]
        private sealed class ReadOnlyWrapper : UniqueStringList, ISerializable
        {

            #region Constants
            private const string ExceptionMessage = "Cannot modify a read-only UniqueStringList";
            #endregion Constants

            #region Fields
            private UniqueStringList _wrappedList;               // Holds the underlying UniqueStringList
            #endregion Fields

            #region Constructors
            private ReadOnlyWrapper(SerializationInfo info, StreamingContext context)
            {
                _wrappedList = (UniqueStringList)info.GetValue("wrappedList", typeof(UniqueStringList));
            }

            internal ReadOnlyWrapper(UniqueStringList list)
                : base(true)
            {
                _wrappedList = list;
            }
            #endregion

            #region Implementation of ISerializable
            void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
            {
                info.AddValue("wrappedList", _wrappedList);
            }
            #endregion

            #region Implementation of IEnumerable
            public override IEnumerator GetEnumerator()
            {
                return _wrappedList.GetEnumerator();
            }
            #endregion

            #region Implementation of IList
            public override bool IsFixedSize
            {
                get { return true; }
            }
            #endregion

            #region Implementation of ICollection
            public override void CopyTo(System.Array array, int index)
            {
                _wrappedList.CopyTo(array, index);
            }

            public override bool IsSynchronized
            {
                get { return _wrappedList.IsSynchronized; }
            }

            public override int Count
            {
                get { return _wrappedList.Count; }
            }

            public override object SyncRoot
            {
                get { return _wrappedList.SyncRoot; }
            }
            #endregion

            #region General Implementation
            public override bool Add(string value)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void AddRange(string[] values)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override string[] AlsoIn(UniqueStringList secondList)
            {
                return _wrappedList.AlsoIn(secondList);
            }

            public override void Clear()
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override bool Contains(string value)
            {
                return _wrappedList.Contains(value);
            }

            public override string EmptyAlias
            {
                get
                {
                    return _wrappedList.EmptyAlias;
                }
                set
                {
                    throw new NotSupportedException(ExceptionMessage);
                }
            }

            public override int IndexOf(string value)
            {
                return _wrappedList.IndexOf(value);
            }

            public override void Insert(int index, string value)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override bool IsCaseSensitive
            {
                get { return _wrappedList.IsCaseSensitive; }
            }

            public override bool IsReadOnly { get { return true; } }

            public override string[] NotIn(UniqueStringList secondList)
            {
                return _wrappedList.NotIn(secondList);
            }
            public override void Remove(string value)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void RemoveAt(int index)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void RemoveRange(string[] values)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void Reverse(int index, int count)
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void Reverse()
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override void Sort()
            {
                throw new NotSupportedException(ExceptionMessage);
            }

            public override string this[int index]
            {
                get { return _wrappedList[index]; }
                set
                {
                    throw new NotSupportedException(ExceptionMessage);
                }
            }

            public override string[] ToArray()
            {
                return _wrappedList.ToArray();
            }

            public override string ToString(string prefix, string separator, string suffix)
            {
                return _wrappedList.ToString(prefix, separator, suffix);
            }

            #endregion General Implementation

        }

        [Serializable]
        public class CaseSensitive : UniqueStringList
        {

            #region Constructors
            protected CaseSensitive(SerializationInfo info, StreamingContext context)
                : this(info.GetInt32("count"))
            {
                AddRange((string[])info.GetValue("strings", typeof(Array)));

                // Retrieve the empty alias
                _emptyAlias = info.GetString("emptyAlias");
            }

            public CaseSensitive() : this(DefaultSize) { }

            public CaseSensitive(string[] values)
                : this(values.Length)
            {
                // Add the initial values into the pre-sized collection
                AddRange(values);
            }

            public CaseSensitive(UniqueStringList values)
                : this(values.Count)
            {
                // Add the initial values into the pre-sized collection
                AddRange(values.ToArray());
            }

            public CaseSensitive(int capacity)
            {
                // Create new containers to the indicated size
                _data = new ArrayList(capacity);
                _lookup = new Hashtable(capacity);
            }

            #endregion Constructors

            #region Case-sensitive specific overrides
            public override int IndexOf(string value)
            {
                // Use the lookup table (for speed) to check whether the item actually exists
                if (!_lookup.Contains(value))
                    // It doesn't so we can return the value directly
                    return -1;
                else
                {
                    // Since we are comparing case-sensitive string we can call IndexOf directly
                    return _data.IndexOf(value);
                }
            }

            public override bool IsCaseSensitive
            {
                get { return true; }
            }
            #endregion

        }
    }
}
